//DOM helper functions
var _absPos = (function(){
	var curleft, curtop;//make func more garbage friendly (no more recursion or thread compatible)
	return function(obj) {
		curleft = curtop = 0;
		if (obj.offsetParent) {
			do {
				curleft += obj.offsetLeft;
				curtop += obj.offsetTop;
			} while (obj = obj.offsetParent);
		}
		return {x:curleft, y:curtop};
	}
})();

//direct access to Box2D API
var   b2Vec2 = Box2D.Common.Math.b2Vec2
	,  b2AABB = Box2D.Collision.b2AABB
	,	b2BodyDef = Box2D.Dynamics.b2BodyDef
	,	b2Body = Box2D.Dynamics.b2Body
	,	b2FixtureDef = Box2D.Dynamics.b2FixtureDef
	,	b2Fixture = Box2D.Dynamics.b2Fixture
	,	b2World = Box2D.Dynamics.b2World
	,	b2MassData = Box2D.Collision.Shapes.b2MassData
	,	b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
	,	b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
	,	b2DebugDraw = Box2D.Dynamics.b2DebugDraw
	,  b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef;
var world;//global singleton

var addMonitorF;
var removeMonitorF;

var PhysSocket = function(setter, getter, compare){
	var self = this;
	Socket.call(this, getter(), compare);
	var oldSet = this.set;
	this.set = function(val){
		if (!compare(val,self.get())){
			setter(val);
			oldSet(val);
		}
	}
	var checkF;
	if (compare)
		checkF = function(){
			var val = getter();
			if (!compare(val,self.get())){
				self.set(val);
			}
		}
	else
		checkF = function(){
			var val = getter();
			if (val!=self.get()){
				self.set(val);
			}
		}
	var activateSocket = function(){
		addMonitorF(self._id, checkF);
	}
	var deactivateSocket = function(){
		removeMonitorF(self._id);
	}
	var oldAdd = this.addOutput;
	this.addOutput = function(receiver){
		oldAdd(receiver);
		activateSocket();
	}
	var oldRem = this.removeOutput;
	this.removeOutput = function(receiver){
		oldRem(receiver);
		if (self._outCount()==0) deactivateSocket();
	}
};

var PhysVec2CtrlImpl = function(){
	var self = this;
	if ('x' in this.sockets){
		this.addOutput(function(){
			var out = self.sockets['x'];
			var oldSet = out.set;
			out.set = function(val){
				if (val!=out.get()){
					oldSet(val);
					if (self._outCount()>0){
						self.set(new b2Vec2(val, self.get().y));
					}
				}
			}
			var handler = new IdObj();
			handler.set = function(val){
				out.set(val.x);
			}
			return handler;
		}());
	}
	if ('y' in this.sockets){
		this.addOutput(function(){
			var out = self.sockets['y'];
			var oldSet = out.set;
			out.set = function(val){
				if (val!=out.get()){
					oldSet(val);
					if (self._outCount()>0){
						self.set(new b2Vec2(self.get().x, val));
					}
				}
			}
			var handler = new IdObj();
			handler.set = function(val){
				out.set(val.y);
			}
			return handler;
		}());
	}
}

var Vec2Compare = function(v1, v2){
	if (v1!=null && v2!= null){
		return (v1.x==v2.x) && (v1.y == v2.y);
	}else{
		return v1==v2;
	}
}
_scalarCtrl['Vec2'] = function(){
	Socket.call(this,new b2Vec2(0,0),Vec2Compare);
}
_registerCtrl('Vec2',{
	'x':'Number',
	'y':'Number'
}, PhysVec2CtrlImpl);

var PhysObjCtrl = function(body){
	//*
	var posGetter = function(){
		return body.GetPosition().Copy();
	}
	this['pos'] = new PhysSocket(body.SetPosition.bind(body), posGetter, Vec2Compare);//*/
	this['angle'] = new PhysSocket(body.SetAngle.bind(body), body.GetAngle.bind(body));
	var velGetter =
	this['vel'] = new PhysSocket(body.SetLinearVelocity.bind(body),
		function(){
			return body.GetLinearVelocity().Copy();
		},
		Vec2Compare);
	this['aVel'] = new PhysSocket(body.SetAngularVelocity.bind(body),
		body.GetAngularVelocity.bind(body));
	this['mat'] = null;
	var self = this;
	this.destroy = function(){
		self['pos'].destroy();
		self['angle'].destroy();
		self['vel'].destroy();
		self['aVel'].destroy();
	}
};

var PhysObjCtrlImpl = function(){
	var self = this;
	if ('impulse' in this.sockets){
		var vector = new _scalarCtrl['Vec2']();
		var point = new _scalarCtrl['Vec2']();
		var signal = new _scalarCtrl['Signal']();
		this.sockets['impulse'] = {
			'signal':signal,
			'vector':vector,
			'point':point
		}
		signal.set = function(){
			self.get()._getBody().ApplyImpulse(vector.get(), point.get());
		}
	}
}

_registerCtrl('PhysObj',{
	'pos':'Vec2',
	'angle':'Number',
	'vel':'Vec2',
	'aVel':'Vec2',
	'impulse':{
		'signal':'Signal',
		'vector':'Vec2',
		'point':'Vec2'
	}
},PhysObjCtrlImpl);

var PhysCircle = function(){
	var bodyRef = null;
	this.ctrls = {
		'PhysObj':new PhysObjCtrl(bodyRef)
	}
}

var PhysBox = function(width, height){
	IdObj.call(this);
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_dynamicBody;
	var fixDef = new b2FixtureDef;
	fixDef.density = 1.0;
	fixDef.friction = 0.5;
	fixDef.restitution = 0.2;

	fixDef.shape = new b2PolygonShape;
	/*fixDef.shape.SetAsBox(
		(Math.random() + 0.1) //half width
		,  (Math.random() + 0.1) //half height
	);*/
	fixDef.shape.SetAsBox(width, height);

	bodyDef.position.x = Math.random() * 10;
    bodyDef.position.y = Math.random() * 10;
	var body = world.CreateBody(bodyDef);
	body.CreateFixture(fixDef);
	var pos = body.GetPosition();

	var self = this;
	this.addDest(function(){
		world.DestroyBody(body);
		var ctrls = self.ctrls;
		for (var ctrl in ctrls){
			ctrls[ctrl].destroy();
		}
	});

	this.ctrls = {
		'Object': new ObjectCtrl(this),
		'PhysObj':new PhysObjCtrl(body)
	}
	this._getBody = function(){
		return body;
	}
}
_registerProto('PhysBox',PhysBox,{
	params:{//same order with constructor!
		'Width':'Number',
		'Height':'Number'
	},
	ctrls:[
		'Object',
		'PhysObj'
	]
});

var PhysInit = function(canvas){
	//add world

	world = new b2World(
		new b2Vec2(0, 9.81)    //gravity
		,  true                 //allow sleep
	);

	var fixDef = new b2FixtureDef;
	fixDef.density = 1.0;
	fixDef.friction = 0.5;
	fixDef.restitution = 0.2;

	var bodyDef = new b2BodyDef;

	//create ground
	bodyDef.type = b2Body.b2_staticBody;
	fixDef.shape = new b2PolygonShape;
	fixDef.shape.SetAsBox(20, 2);
	bodyDef.position.Set(10, 400 / 30 + 1.8);
	world.CreateBody(bodyDef).CreateFixture(fixDef);
	bodyDef.position.Set(10, -1.8);
	world.CreateBody(bodyDef).CreateFixture(fixDef);
	fixDef.shape.SetAsBox(2, 14);
	bodyDef.position.Set(-1.8, 13);
	world.CreateBody(bodyDef).CreateFixture(fixDef);
	bodyDef.position.Set(21.8, 13);
	world.CreateBody(bodyDef).CreateFixture(fixDef);


	//setup debug draw
	var debugDraw = new b2DebugDraw();
	debugDraw.SetSprite(canvas.getContext("2d"));
	debugDraw.SetDrawScale(30.0);
	debugDraw.SetFillAlpha(0.5);
	debugDraw.SetLineThickness(1.0);
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
	world.SetDebugDraw(debugDraw);

	var monitors = {};
	addMonitorF = function(socket_id,f){
		monitors[socket_id] = f;
	}
	removeMonitorF = function(socket_id){
		delete monitors[socket_id];
	}
	//update world
	window.setInterval( function () {
		for (var k in monitors){
			monitors[k]();
		}

		world.Step(1 / 60, 10, 10);
		world.DrawDebugData();
		world.ClearForces();
	}, 1000 / 60.0);

	//grab control
	(function(){
		function getBodyAtMouse(mouseX, mouseY){
			var mousePVec = new b2Vec2(mouseX, mouseY);
			var aabb = new b2AABB();
			aabb.lowerBound.Set(mouseX - 0.001, mouseY - 0.001);
			aabb.upperBound.Set(mouseX + 0.001, mouseY + 0.001);

			// Query the world for overlapping shapes.

			var selectedBody = null;
			world.QueryAABB(
				function getBodyCB(fixture) {
					if(fixture.GetBody().GetType() != b2Body.b2_staticBody) {
						if(fixture.GetShape().TestPoint(
							fixture.GetBody().GetTransform(), mousePVec)) {
							selectedBody = fixture.GetBody();
							return false;
						}
					}
					return true;
				},
				aabb);
			return selectedBody;
		}
		var grabControl = new function(){
			this.install = function(){
				var mouseDown = false;
				var mX, mY;
				var canvasPos = _absPos(_canvas);
				var mouseMoveHandler = function(e){
					mX = (e.pageX - canvasPos.x) / 30;
					mY = (e.pageY - canvasPos.y) / 30;
				}
				document.addEventListener('mousedown', function(e){
					mouseDown = true;
					mouseMoveHandler(e);
					document.addEventListener('mousemove', mouseMoveHandler,
						false);
				}, false);
				document.addEventListener('mouseup', function(e){
					mouseDown = false;
					document.removeEventListener('mousemove', mouseMoveHandler,
						false);
				}, false);
				var mouseJoint;
				addMonitorF(NewId(), function(){
					if (mouseDown && (!mouseJoint)){
						var body = getBodyAtMouse(mX, mY);
						if(body) {
							var md = new b2MouseJointDef();
							md.bodyA = world.GetGroundBody();
							md.bodyB = body;
							md.target.Set(mX, mY);
							md.collideConnected = true;
							md.maxForce = 300.0 * body.GetMass();
							mouseJoint = world.CreateJoint(md);
							body.SetAwake(true);
						}
					}

					if(mouseJoint) {
						if(mouseDown) {
							mouseJoint.SetTarget(new b2Vec2(mX, mY));
						} else {
							world.DestroyJoint(mouseJoint);
							mouseJoint = null;
						}
					}
				});
			}
			this.uninstall = function(){
				//TODO: remove hooks of grabControl
			}
		}
		SetControlMode(grabControl);
	})();

}

//input processing
var ArrowControl = function(){
	var arrowX = new _scalarCtrl['Number']();
	var arrowY = new _scalarCtrl['Number']();
	var signal = new _scalarCtrl['Signal']();

	document.onkeyup = function(e){
		switch (e.keyCode) {
			case 37://left
				arrowX.set(-1 * 0.3);
				signal.set({});//singal!
				break;
			case 38://up
				arrowY.set(-1 * 0.3);
				signal.set({});//singal!
				break;
			case 39://right
				arrowX.set(+1 * 0.3);
				signal.set({});//singal!
				break;
			case 40://down
				arrowY.set(1 * 0.3);
				signal.set({});//singal!
				break;
		}
	}

	this['x'] = arrowX;
	this['y'] = arrowY;
	this['signal'] = signal;
}

rootload = function(){
	//for firefox compatability
	_canvas = document.getElementById('_canvas');
	_plate = document.getElementById('_plate');

	PhysInit(_canvas);
	var box = _object('PhysBox',[0.6, 1.0]);

	var oCtrl = new _ctrlImpl('Object', ['Destroy']);
	oCtrl.connect(box);

	var bCtrl = new _ctrlImpl('PhysObj',['pos','impulse']);
	//bCtrl.connectReverse(box);
	bCtrl.set(box);
	var bY = new _ctrlImpl('Vec2',['y']);
	bCtrl.sockets['pos'].addOutput(bY);

	//var desY = new NumberCtrlDesigner(bY.sockets['y']);
	//_plate.appendChild(desY.getElem());

	var arrow = new ArrowControl();
	//bCtrl.sockets['impulse']['vector'].set(new b2Vec2(0,-5));
	bCtrl.sockets['impulse']['point'].set(new b2Vec2(0,0));
	arrow['signal'].addOutput(bCtrl.sockets['impulse']['signal']);

	var impul = new _ctrlImpl('Vec2',['x','y']);
	impul.addOutput(bCtrl.sockets['impulse']['vector']);
	arrow['x'].addOutput(impul.sockets['x']);
	arrow['y'].addOutput(impul.sockets['y']);

	var createChip = new ProtoImpl('PhysBox', true);
	//var grCtrl = new SocketGroupImpl(createChip.outs, ['Count']);

	//var a = new ScalarCtrlDesigner('Vec2', impul);
}
